﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using CIP4.Simple;
using Plossum.CommandLine;
using CIP4.Common;

namespace CIP4.BusDriver
{
    class BusDriverProgram
    {
        public const string APPNAME = "CIP4.BusDriver";

        static OptionsType _options;

        static StreamWriter _logFile;
        static StreamWriter LogFile
        {
            get
            {
                if (_logFile == null)
                {
                    _logFile = string.IsNullOrEmpty(_options.LogFileNameQualifier) ?
                        new StreamWriter(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", APPNAME), true) :
                        new StreamWriter(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.{1}.log", APPNAME, _options.LogFileNameQualifier), true);

                    AlwaysWriteLog(MajorEnum.Information, ">>>>>> Starting " + APPNAME);
                }
                return _logFile;
            }
        }

        static DateTime _startTime;

        static void Main()
        {
            _startTime = DateTime.Now;

            if (!CheckArgs())
            {
                Environment.ExitCode = 8000;
                return;
            }
            AlwaysWriteLog(MajorEnum.Information, _options.ToString());

            if (_options.Confirm)
            {
                ShowInformational(_options.ToString());
                ShowInformational("Enter Yes to run with these options, No to exit");
                string response = Console.ReadLine()??string.Empty;
                if (!(response.ToLower() == "y" || response.ToLower() == "yes"))
                {
                    Environment.ExitCode = 1000;
                    return;
                }
            }

            if (!GetMessageReceiver())
                return;

            ShowInformational(string.Format("Receiving messages on bus {0}", _options.BusId));

            do
            {
                Console.WriteLine("Enter 'q' to quit");
            } while ((Console.ReadLine() ?? string.Empty) != "q");

        }

        private static Dictionary<string,CIP4ConnectionAttribute> MakeConections()
        {
            Dictionary<string, CIP4ConnectionAttribute> atts = new Dictionary<string, CIP4ConnectionAttribute>();
            foreach (KeyValuePair<string,string> kvp in _options.ConnectionStrings)
            {
                if (!kvp.Value.Contains("ProviderKind"))
                {
                    ShowError(string.Format("You must provide a ProviderKind property for each connection: {0} - {1}", kvp.Key, kvp.Value));                    
                }
                else
                {
                    CIP4ConnectionAttribute att = CIP4ConnectionAttribute.GetConnectionFromString(kvp.Value);
                    atts.Add(kvp.Key, att);
                }
            }

            return atts;
        }

        private static object _messageReceiver;

        private static  bool GetMessageReceiver()
        {
            try
            {
                Assembly ass = Globals.AssemblyResolve(Path.GetFileNameWithoutExtension(_options.ProviderAssemblyName));

                Type messageReceiverType = null;

                foreach (Type t in ass.GetTypes())
                {
                    if (t.Name != "RequestMessageReceiver") continue;
                    messageReceiverType = t;
                    break;
                }

                if (messageReceiverType == null)
                {
                    ShowError(string.Format("Unable to find RequestMessageReceiver in {0}", _options.ProviderAssemblyName));
                    return false;
                }

                Assembly itemAss = Assembly.LoadFile(_options.ItemAssemblyName);

                ConstructorInfo constructor = messageReceiverType.GetConstructor(new[] { typeof(string), typeof(LoggerDelegate), typeof(Assembly), typeof(Dictionary<string, CIP4ConnectionAttribute>) });

                _messageReceiver = constructor.Invoke(new object[] { _options.BusId, new LoggerDelegate(Logger), itemAss, MakeConections() });

                if (_messageReceiver == null)
                {
                    ShowError("object initialize failed");
                    return false;
                }
                return true;
            }
            catch (Exception ex)
            {
                ShowError(String.Format("Error initializing BusDriver: {0}", ex));
                return false;
            }
        }

        #region message handlers

        private static LoggerResponseEnum Logger(MajorEnum entryType, MinorEnum minorEntryType, string message, object[] paras)
        {
            if (_options.LoggingLevel >= entryType)
            {
                lock (LogFile)
                {
                    string fullMessage = string.Format(message, paras);
                    if (entryType == MajorEnum.Information)
                        ShowInformational(fullMessage);
                    else if (entryType == MajorEnum.Warning)
                        ShowWarning(fullMessage);
                    else if (entryType == MajorEnum.Error)
                        ShowError(fullMessage);
                    else if (entryType == MajorEnum.Noise)
                        Console.Write(string.Format("\r{0} - {1}",
                            TimeSpanString(DateTime.Now - _startTime),
                            fullMessage.Substring(0, Math.Min(Console.WindowWidth - 12, fullMessage.Length))));
                    else
                        Console.WriteLine(fullMessage);
                    WriteLog(MajorEnum.Information, fullMessage);
                }
            }
            if (entryType != MajorEnum.Error)
                return LoggerResponseEnum.keepGoing;
            return LoggerResponseEnum.giveUp;
        }

        private static string TimeSpanString(TimeSpan value)
        {
            return string.Format("{0,2:00}:{1,2:00}:{2,2:00}", value.Hours, value.Minutes, value.Seconds);
        }

        private static void ShowError(string message)
        {
            lock (LogFile)
            {
                WriteLog(MajorEnum.Error, message);
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowWarning(string message)
        {
            lock (LogFile)
            {
                if (_options.LoggingLevel >= MajorEnum.Warning)
                    WriteLog(MajorEnum.Warning, message);
                Console.BackgroundColor = ConsoleColor.DarkYellow;
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine(message);
                Console.ResetColor();
            }
        }

        private static void ShowInformational(string message)
        {
            Console.WriteLine(message);
        }

        private static string _lastMessage;

        private static void WriteLog(MajorEnum level, string message)
        {
            if (string.IsNullOrEmpty(message) || message.Trim().Length == 0 || message.Trim() == _lastMessage)
                return;
            _lastMessage = message.Trim();
            AlwaysWriteLog(level, message);
        }

        private static void AlwaysWriteLog(MajorEnum level, string message)
        {
            _lastMessage = message.Trim();
            LogFile.WriteLine(string.Format("{0} - {1}: {2}", DateTime.Now.ToString("MM:dd hh:mm:ss.fff"), level, message.Trim()));
            LogFile.Flush();
        }

        private static bool CheckArgs()
        {
            bool result = true;
            _options = new OptionsType();

            CommandLineParser parser = new CommandLineParser(_options);
            parser.Parse();

            if (parser.HasErrors)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                foreach (object err in parser.Errors)
                    Console.WriteLine(err.ToString());
                Console.ResetColor();
                result = false; 
            }
            if (parser.RemainingArguments.Count > 0)
            {
                Console.BackgroundColor = ConsoleColor.DarkRed;
                Console.ForegroundColor = ConsoleColor.White;
                Console.Write("Unrecognized input: ");
                foreach (string s in parser.RemainingArguments)
                    Console.Write(s + " ");
                Console.WriteLine();
                Console.ResetColor();
                result = false; 
            }

            Console.WriteLine(parser.UsageInfo.GetHeaderAsString(GetWidth() - 2));

            if (_options.Help)
            {
                Console.WriteLine(parser.UsageInfo.GetOptionsAsString(GetWidth() - 2));
                Console.WriteLine("Defaults:");
                Console.WriteLine(_options.ToString());
                Console.WriteLine(string.Format("Logging to {0}", Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + string.Format("\\{0}.log", APPNAME)));
                result = false; 
            }

            return result;
        }

        private static int GetWidth()
        {
            try
            {
                return Console.WindowWidth;
            }
            catch
            {
                return 72;
            }
        }

        #endregion
    }
}
